<template>
    <div>
        <!-- 上传模块 -->
        <el-upload
            ref="upload"
            class="upload-el"
            v-bind="$attrs"
            :class="{
                // 已选文件数量大于或等于最大允许上传个数时隐藏文件选择功能
                'none-upload': fullHideTrigger && fileList.length >= limit,
                // 禁用时增加禁用样式
                disabled: disabled
            }"
            :action="action"
            :headers="headers"
            :accept="accept"
            :limit="limit"
            :file-list.sync="fileList"
            :list-type="listType"
            :disabled="disabled"
            :auto-upload="autoUpload"
            :on-exceed="onExceed"
            :on-change="onChange"
            :before-upload="beforeUpload"
            :on-success="onSuccess || onUploadSuccess"
            :before-remove="onBeforeRemove"
            :on-remove="onRemove"
            v-on="$listeners"
        >
            <!-- 触发文件选择框插槽 -->
            <template slot="trigger">
                <slot name="trigger"><i class="icon-upload"></i></slot>
            </template>
            <!-- 提示语插槽 -->
            <div v-if="!disabled" slot="tip">
                <div v-if="$slots.tip" class="tip">
                    <slot name="tip"></slot>
                </div>
            </div>
            <!-- 如果listType是'picture-card'自定义展示模式 -->
            <template
                v-if="listType == 'picture-card'"
                slot="file"
                slot-scope="{ file }"
            >
                <uxImage
                    :src="file.url"
                    :disabled="disabled"
                    @remove="onRemove(file, fileList)"
                />
            </template>
        </el-upload>
        <!-- 隐藏表单用于支持验证 -->
        <el-input v-show="false" v-model="inputValue"></el-input>
    </div>
</template>
<script>
import { defineComponent } from '@vue/composition-api';
import { useVModel } from '@vueuse/core';

import { find, floor, concat, compact, map, last, get, forEach } from 'lodash';
import { isEmpty, swapArray } from '@/utils';
// todo 可以按需引入
import Sortable from 'sortablejs';
import uxImage from '@/components/image';

import userCookies from '@/utils/data/cookies';
import { tokenName } from '@/setting';
export default defineComponent({
    name: 'UxUpload',
    components: { uxImage },
    model: {
        prop: 'value',
        event: 'updateValue'
    },
    props: {
        // 表单绑定值(保证用法与普通表单组件一致)
        value: {
            type: String,
            default: ''
        },
        // 文件列表数据
        fileData: {
            type: Array,
            default: function() {
                return [];
            }
        },
        // 允许上传类型（默认为已配置全部类型）
        // 可用类型由rules配置决定
        acceptType: {
            type: Array,
            default: function() {
                return ['pic', 'ppt', 'txt', 'doc', 'pdf', 'xlsx'];
            }
        },
        // 上传的地址
        action: {
            type: String
        },
        // 是否自动上传
        // 如果关闭会在on-change钩子中校验文件格式，文件大小等
        // v-model绑的值是文件名称
        autoUpload: {
            type: Boolean,
            default: true
        },
        // 是否禁用
        disabled: {
            type: Boolean,
            default: false
        },
        // 是否支持拖拽排序
        isDragSort: {
            type: Boolean,
            default: false
        },
        // 文件数量达到最大允许上传个数时是否隐藏上传按钮
        fullHideTrigger: {
            type: Boolean,
            default: false
        },
        // 上传文件地址前缀
        fileBasePath: {
            type: String,
            default: ''
        },
        // 设置上传的请求头部
        headers: {
            type: Object,
            default: function() {
                const data = {};
                const token = userCookies.get();
                if (token) {
                    data[tokenName] = `${token}`;
                }
                return data;
            }
        },
        // 文件列表的类型
        listType: {
            type: String,
            default: ''
        },
        // 最大允许上传个数
        limit: {
            type: Number,
            default: 50
        },
        // 允许上传文件大小(单位b)
        maxSize: {
            type: Number,
            default: 1024 * 1024 * 20
        },
        // 上传成功钩子,不配置时会走组件内部方法onUploadSuccess
        onSuccess: {
            type: Function,
            default: null
        },
        // 自定义文件格式配置
        rules: {
            type: Array,
            default: function() {
                return [];
            }
        },
        // 请求状态判断字段名称
        successProperty: {
            type: String,
            default: 'code'
        },
        // 返回消息字段名称
        messageProperty: {
            type: String,
            default: 'msg'
        },
        // 文件地址字段名称
        pathProperty: {
            type: String,
            default: 'data.file_name'
        },
        // 请求成功状态码
        successCode: {
            type: [String, Number],
            default: 200
        }
    },
    setup(props, { emit }) {
        // useVModel函数代理了父子之间的数据交互
        const inputValue = useVModel(props, 'value', emit);
        return { inputValue };
    },
    data() {
        return {
            // 上传文件列表,自动生成
            fileList: [],
            // 默认文件格式配置
            defaultRules: [
                {
                    name: 'xlsx',
                    // 校验规则
                    rule: [
                        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                        'application/vnd.ms-excel'
                    ],
                    // 文件后缀
                    suffix: ['.xlsx', '.xls']
                },
                {
                    name: 'doc',
                    rule: [
                        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                        'application/msword'
                    ],
                    suffix: ['.doc', '.docx']
                },
                {
                    name: 'pdf',
                    rule: ['application/pdf'],
                    suffix: ['.pdf']
                },
                {
                    name: 'pic',
                    rule: ['image/jpeg', 'image/jpg', 'image/png'],
                    suffix: ['.jpeg', '.jpg', '.png']
                },
                {
                    name: 'ppt',
                    rule: [
                        'application/vnd.ms-powerpoint',
                        'application/vnd.openxmlformats-officedocument.presentationml.presentation'
                    ],
                    suffix: ['.ppt', '.pptx']
                },
                {
                    name: 'txt',
                    rule: ['text/plain'],
                    suffix: ['.txt']
                }
            ],
            // 允许上传类型(根据rules自动生成)
            accept: '',
            // 允许上传类型校验规则(根据rules自动生成)
            acceptRules: [],
            // 允许上传类型提示文案(根据rules自动生成)
            acceptMessage: ''
        };
    },
    computed: {
        // 文件格式配置
        finalRules() {
            return [...this.rules, ...this.defaultRules];
        }
    },
    watch: {
        // 监听表单绑定值
        // 将字符串数据转换为可识别的数据格式
        fileData: {
            immediate: true,
            handler(v) {
                const fileList = [];
                forEach(v, ({ id, original, path }) => {
                    fileList.push({ id, path, url: path, name: original });
                });
                this.fileList = fileList;
            }
        },
        // 监听允许上传类型生成校验规则
        acceptType: {
            immediate: true,
            handler(v) {
                if (!isEmpty(v)) {
                    // 根据允许上传类型确定校验规则文案
                    const me = this,
                        rules = me.finalRules;
                    let accept, checkList;
                    // 循环遍历生成校验规则数据
                    v.forEach((name) => {
                        const item = find(rules, { name });
                        if (item) {
                            // 上传可选类型
                            accept = concat(accept, item.suffix);
                            // 可选类型校验规则
                            checkList = concat(checkList, item.rule);
                        }
                    });
                    accept = compact(accept).toString();
                    // 设置可选类型
                    me.accept = accept;
                    // 设置校验规则
                    me.acceptRules = checkList;
                    // 设置提示文案
                    me.acceptMessage = `只能上传${accept
                        .replace(/\./g, '')
                        .replace(/\,/g, '、')}格式文件`;
                }
            }
        },
        // 监听文件列表变化
        fileList(v) {
            const me = this,
                data = map(v, 'path');
            // 更新数据
            me.$emit('updateValue', data.toString());
        }
    },
    mounted() {
        this.initDragSort();
    },
    methods: {
        /**
         * @description 拖拽排序初始化
         */
        initDragSort() {
            if (this.isDragSort) {
                // 支持拖拽排序
                const el = this.$refs.upload.$el.querySelectorAll(
                    '.el-upload-list'
                )[0];
                Sortable.create(el, {
                    onEnd: ({ oldIndex, newIndex }) => {
                        // 交换位置
                        swapArray(this.fileList, oldIndex, newIndex);
                    }
                });
            }
        },
        /**
         * @description 文件超出个数限制时
         * @param {object} files 当前选中文件
         * @param {object} fileList 已有文件集合
         */
        onExceed(files, fileList) {
            this.$message.warning(
                `当前限制选择 ${this.limit} 个文件，本次选择了 ${
                    files.length
                } 个文件，共选择了 ${files.length + fileList.length} 个文件`
            );
        },
        /**
         * 上传文件之前进行文件格式校验
         * @param {any} [file] 已选文件
         *
         */
        beforeUpload(file) {
            const me = this,
                is = me.acceptRules.includes(file.type);
            // 文件类型检验
            if (!is) {
                this.$message.error(me.acceptMessage);
                return false;
            }
            // 文件大小校验
            if (file.size > me.maxSize) {
                this.$message.error(
                    `上传大小不能超过${me.sizeFormat(me.maxSize)}`
                );
                return false;
            }
            return is;
        },
        /**
         * @description 上传文件成功后处理
         * @param {*} response 服务端返回信息
         * @param {*} file 已选文件
         * @param {*} fileList 已有文件集合
         * @return {*}
         */
        onUploadSuccess(response, file, fileList) {
            // 检查是否上传成功
            if (this.checkResponse(response)) {
                const item = last(fileList);
                // 后端返回文件路径,用于v-model取值
                item.path = get(response, this.pathProperty);
                console.log(item.path);
            } else {
                // 上传失败删除
                const index = fileList.findIndex((item) => {
                    // 排除已上传的文件，避免删除掉同名文件
                    !item.path && item.name === file.name;
                });
                fileList.splice(index, 1);
            }
            // 不能直接操作this.fileList，交互上会有问题
            this.fileList = fileList;
        },
        /**
         * @description 删除文件前
         * @param {*} file 当前文件
         * @return {*}
         */
        onBeforeRemove(file) {
            if (file && file.status === 'success') {
                return this.$confirm(`确定移除 ${file.name}？`);
            }
            return true;
        },
        /**
         * @description 删除文件
         * @param {*} path 文件路径 path/uid 存在一个即可
         * @param {*} uid 文件uid
         * @return {*}
         */
        onRemove({ path, uid }, fileList) {
            const index = fileList.findIndex((item) => {
                // 没有上传则根据uid删除
                if (!path) {
                    return item.uid === uid;
                }
                // 根据服务端路径来删除文件
                return item.path === path;
            });
            fileList.splice(index, 1);
            // 不能直接操作this.fileList，交互上会有问题
            this.fileList = fileList;
        },
        onChange(file, fileList) {
            const me = this;
            if (!this.autoUpload && file.status == 'ready') {
                const is = me.beforeUpload(file.raw);
                if (!is) {
                    this.onRemove(file, fileList);
                } else {
                    this.fileList = fileList;
                }
            }
        },
        /**
         * 格式化文件大小，将单位从b按规则转化为其他单位
         * @param {number} [value] 待转化数据
         *
         */
        sizeFormat(value) {
            if (!value) {
                return '';
            }
            let unit = '',
                i = 0;
            const k = 1024;
            if (value >= k) {
                // 获取转换下标
                i = Math.floor(Math.log(value) / Math.log(k));
            }
            // 获取转换后单位
            unit = ['B', 'KB', 'MB', 'GB'][i];
            // 根据转换下标获取转换后的值
            value = value / Math.pow(k, i);
            // console.log('第一次转换:', value);
            // 根据配置设置小数精度
            value = floor(value, 1);
            return value + unit;
        },
        /**
         * 根据文件路径获取文件名称
         * 并去掉前面的时间戳
         * @param {string} [url] 文件路径
         *
         */
        getName(url) {
            return url.replace(/(.*\/)*([^.]+)/i, '$2').slice(14);
        },
        /**
         * @description  检查文件上传结果
         * @param {number} code 编码
         * @param {string} msg 消息
         * @return {boolean}
         */
        checkResponse(data) {
            const { successProperty, messageProperty, successCode } = this;
            const code = get(data, successProperty);
            if (code !== successCode) {
                let msg = get(data, messageProperty);
                if (isEmpty(msg)) {
                    msg = '文件上传失败';
                }
                this.$message.error(msg);
            }
            return code === successCode;
        },
        // 手动上传文件
        /**
         * @description 手动上传文件列表
         */
        submit() {
            this.$refs.upload.submit();
        },
        /**
         * @description  取消上传请求
         * @param {*} file fileList 中的 file 对象
         * @return {*}
         */
        abort(file) {
            this.$refs.upload.abort(file);
        },
        /**
         * @description  清空已上传的文件列表（该方法不支持在 before-upload 中调用）
         */
        clearFiles() {
            this.$refs.upload.clearFiles();
        }
    }
});
</script>
<style scoped lang="scss">
@import '@/assets/sass/all.scss';
.upload-el {
    @extend .box-row, .box-flex-wrap;
    line-height: 0;
}
::v-deep.disabled {
    .el-upload {
        display: none;
    }
}
::v-deep .none-upload {
    .el-upload {
        display: none;
    }
}
::v-deep .el-upload-list--picture-card {
    .el-upload-list__item {
        width: 115px;
        height: 115px;
        margin: 0 8px 0 0;
    }
}
::v-deep .el-upload {
    width: 115px;
    height: 115px;
    line-height: 115px;
    background-color: #f4fbff;
    border: 1px dashed #0093dd;
    border-radius: 6px;
    font-size: 28px;
    color: #0093dd;
}
.tip {
    @extend .box-column, .box-center;
    margin-left: 20px;
    font-size: 12px;
    color: #ababab;
    line-height: 16px;
    height: 100%;
}

::v-deep .el-upload-list:not(.el-upload-list--picture-card) {
    width: 100%;
    .el-upload-list__item {
        // 关闭动画效果避免交互效果不好
        transition: none !important;
        text-align: left;
    }
}
</style>
